package velox.jcr.mapper.declarative;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.jcr.Node;
import javax.jcr.RepositoryException;

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;

import velox.jcr.mapper.CachingMapper;
import velox.jcr.mapper.CascadeType;
import velox.jcr.mapper.Loader;
import velox.jcr.mapper.ValueMapper;

public class DeclarativeMapper<T> implements CachingMapper<T, Map<String, Object>> {

	private String discriminatorProperty;
	private boolean cache;
	private Class<?> mappedClass;
	protected List<Mapping> mappings = new ArrayList<Mapping>();
	private IdMapping idMapping;
	
	public void setClass(Class<?> type) {
		this.mappedClass = type;
	}
	
	public void setDiscriminatorProperty(String discriminatorProperty) {
		this.discriminatorProperty = discriminatorProperty;
	}

	public void setIdMapping(String javaProperty) {
		idMapping = new IdMapping(javaProperty);
		mappings.add(idMapping);
	}

	public void setIdMapping(String javaProperty, String name) {
		idMapping = new IdMapping(javaProperty, name);
		mappings.add(idMapping);
	}
	
	public void addBasicMapping(String property) {
		addBasicMapping(property, property);
	}
	
	public void addBasicMapping(String javaProperty, String jcrProperty) {
		mappings.add(new BasicMapping(javaProperty, jcrProperty)); 
	}
	
	public void addParentMapping(String javaProperty, Class<?> type, Loader<String> loader) {
		addParentMapping(javaProperty, type, CascadeType.NONE, loader);
	}
	
	public void addParentMapping(String javaProperty, Class<?> type, CascadeType cascade, Loader<String> loader) {
		mappings.add(new ParentMapping(javaProperty, type, loader, cascade));
	}
	
	public void addDescendantsMapping(String javaProperty, String containerName, Class<?> type, Loader<List<String>> loader) {
		addDescendantsMapping(javaProperty, containerName, type, CascadeType.ALL, loader);
	}
	
	public void addDescendantsMapping(String javaProperty, String containerName, Class<?> type, CascadeType cascade, Loader<List<String>> loader) {
		mappings.add(new DescendantsMapping(javaProperty, containerName, type, loader, cascade));
	}

	public void addCollectionMapping(String javaProperty, ValueMapper valueMapper) {
		mappings.add(new CollectionMapping(javaProperty, javaProperty, valueMapper));
	}
	
	public void addCollectionMapping(String javaProperty, String propertyName, ValueMapper valueMapper) {
		mappings.add(new CollectionMapping(javaProperty, propertyName, valueMapper));
	}

	public String getId(Object t) {
		return idMapping.getId(new BeanWrapperImpl(t));
	}

	public String getName(Object t) {
		return idMapping.getName(new BeanWrapperImpl(t));
	}

	
	public void setCache(boolean cache) {
		this.cache = true;
	}
	
	public String getDiscriminator(Node n) throws RepositoryException {
		if (discriminatorProperty == null) {
			return null;
		}
		return n.getProperty(discriminatorProperty).getString();
	}

	public Mapping getMapping(String javaProperty) {
		for (Mapping mapping : mappings) {
			if (mapping.getJavaProperty().equals(javaProperty)) {
				return mapping;
			}
		}
		return null;
	}
	
	
	@SuppressWarnings("unchecked")
	public T read(Node node) throws RepositoryException {
		BeanWrapper beanWrapper = new BeanWrapperImpl(mappedClass);
		for (Mapping mapping : mappings) {
			mapping.read(node, beanWrapper);
		}
		return (T) beanWrapper.getWrappedInstance();
	}

	public void write(T t, Node node, boolean create)
			throws RepositoryException {
		BeanWrapper beanWrapper = new BeanWrapperImpl(t);
		for (Mapping mapping : mappings) {
			mapping.write(node, beanWrapper, create);
		}
	}

	@SuppressWarnings("unchecked")
	public T deserialize(Map<String, Object> cache) {
		BeanWrapper beanWrapper = new BeanWrapperImpl(mappedClass);
		for (Mapping mapping : mappings) {
			mapping.readFromCache(cache, beanWrapper);
		}
		return (T) beanWrapper.getWrappedInstance();
	}

	public Map<String, Object> serialize(T t) {
		if (! cache) return null;
		
		Map<String, Object> cache = new HashMap<String, Object>(10);
		BeanWrapper beanWrapper = new BeanWrapperImpl(t);
		for (Mapping mapping : mappings) {
			mapping.writeToCache(cache, beanWrapper);
		}
		return cache;
	}
	
	
}
